﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ZackFlame.SimpleBrainFuck.Controls
{
    internal sealed class InteractiveConsoleReader : TextReader
    {
        AutoResetEvent gotInput = new AutoResetEvent(false);
        StringBuilder inputText = new StringBuilder();

        object syncRoot = new object();
        bool isDisposed = false;

        private Control inputControl;

        public Control Input
        {
            get { return inputControl; }
            set
            {
                if (isDisposed)
                    return;

                if (value == null)
                    throw new ArgumentNullException();
                else if (value == inputControl)
                    return;

                if (inputControl != null)
                {
                    inputControl.KeyDown -= Input_KeyDown;
                    ClearInput();
                }

                inputControl = value;
                inputControl.KeyDown += Input_KeyDown;
            }
        }

        public bool HasBufferedInput
        {
            get { return inputText.Length > 0; }
        }

        public bool DoWaitForInput { get; set; }

        public event EventHandler WaitInput;

        public event EventHandler GotInput;

        public event EventHandler<ReadEventArgs> LineReaded;

        public InteractiveConsoleReader(Control input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            Input = input;
            DoWaitForInput = false;
        }

        private void OnWaitInput()
        {
            var eventTemp = WaitInput;
            if (eventTemp != null)
            {
                eventTemp(this, EventArgs.Empty);
            }
        }

        private void OnGotInput()
        {
            var eventTemp = GotInput;
            if (eventTemp != null)
            {
                eventTemp(this, EventArgs.Empty);
            }
        }

        private void OnLineReaded(string readedText)
        {
            var eventTemp = LineReaded;
            if (eventTemp != null)
            {
                eventTemp(this, new ReadEventArgs(readedText));
            }
        }

        private void WaitForInput()
        {
            if (isDisposed)
                return;

            Input.Invoke(new Action(() =>
            {
                OnWaitInput();
            }));
            gotInput.WaitOne();
        }

        private void Input_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter && DoWaitForInput)
            {
                e.SuppressKeyPress = true;

                string text = Input.Text + "\n";

                OnGotInput();
                OnLineReaded(text);

                inputText.Append(text);
                Input.Text = string.Empty;

                gotInput.Set();
            }
        }

        public override int Read()
        {
            lock (syncRoot)
            {
                WaitForInput();

                if (isDisposed)
                    return -1;

                char ch = inputText[0];
                inputText = inputText.Remove(0, 1);

                if (inputText.Length > 0)
                    gotInput.Set();

                return ch;
            }
        }

        public override string ReadLine()
        {
            lock (syncRoot)
            {
                WaitForInput();

                if (isDisposed)
                    return null;

                string text = inputText.ToString();
                inputText.Length = 0;

                return text;
            }
        }

        public void ClearInput()
        {
            lock (syncRoot)
            {
                gotInput.Reset();
                inputText.Length = 0;
            }
        }

        protected override void Dispose(bool disposing)
        {
            isDisposed = true;
            inputControl.KeyDown -= Input_KeyDown;
            ClearInput();

            WaitInput = null;
            GotInput = null;
            LineReaded = null;

            base.Dispose(disposing);
        }
    }
}
